#include <iostream>
#include "trace.h"
#include "general.h"
#include "constants.h"
#include <deque>
#include <vector>
#include <ctime>
#include <string>

using namespace std;

struct CSMA_node
{
	Position pos;
	int globalID;
	int domainID;

	int totalBuffer;
	int totalSend;
	int totalReceive;
	int totalCollision;
	deque<Traffic> nodeTraffic;

	int state;

	bool isSender;
	int sendTo;
	int receiveFrom;

	int conWin;

	int countingDown;
	int prevCountingDown;
};

struct CSMA
{
/*
DIFS-CW-DATA-SIFS-ACK
*/

	CSMA_node csma_nodes[2][MAX_NODE];
	vector<int> csma_neighborList[MAX_NODE];	// keep the neighbors
	Trace trace;
	int nodeNum;
	int t_data;				// time needed for data transmission
	ofstream msgout;

	int receiveCount;
	int sendCount;
	int collisionCount;
	int time;

	void initial(Trace trace, int datarate = 6)
	{
		this->trace = trace;
		this->nodeNum = trace.nodeNum;
		memset(csma_nodes, 0, sizeof(CSMA_node));

		this->t_data = 1500 * 8 / datarate;

		// initial nodes' location, id, etc.
		for (int i = 0; i < trace.nodeNum; i++)
		{
			for (int j = 0; j < 2; j++)
			{
				csma_nodes[j][i].globalID = i;
				csma_nodes[j][i].domainID = trace.nodes_id[i];
				csma_nodes[j][i].pos = trace.nodes_position[i];
				csma_nodes[j][i].isSender = false;
				csma_nodes[j][i].totalBuffer = 0;
				csma_nodes[j][i].totalCollision = 0;
				csma_nodes[j][i].totalReceive = 0;
				csma_nodes[j][i].totalSend = 0;
				csma_nodes[j][i].nodeTraffic.clear();
				csma_nodes[j][i].state = STATE_IDLE;
				csma_nodes[j][i].conWin = CW_MIN;
			}
		}

		// initial nodes' neighbors
		for (int i = 0; i < nodeNum; i++)
		{
			csma_neighborList[i].clear();
			for (int j = 0; j < nodeNum; j++)
			{
				if (i != j && csma_nodes[0][i].pos.isNeighbor(csma_nodes[0][j].pos))
				{
					csma_neighborList[i].push_back(j);
				}
			}
		}
		char filename[256];
		sprintf_s(filename, 256, "csma_debug_%d_%d.txt", datarate, COMM_RANGE);
		msgout.open(filename);
	}

	// run the camaca_mac, for runtime (in millisecond)
	void start_mac(int runtime = 0)
	{
		int seed = (unsigned int)std::time(NULL);
		srand(seed);
		msgout << seed << endl;

		time = 0;
		while (time < runtime * 1000 || runtime == 0)		// runtime == 0: wait until all over
		{
			time++;

			// check traffic trace for all the nodes
			if (!trace.nodes_traffic.empty())
			{
				while (trace.nodes_traffic[0].time_slot <= time)
				{
					int sender = trace.nodes_traffic[0].sender_id;
					int pkt = trace.nodes_traffic[0].pkt_number;
					for (int i = 0; i < 2; i++)
					{
						csma_nodes[i][sender].totalBuffer += pkt;
						csma_nodes[i][sender].nodeTraffic.push_back(trace.nodes_traffic[0]);
					}
					trace.nodes_traffic.pop_front();
					if (trace.nodes_traffic.empty())
						break;
				}
			}
			else
			{
				int shouldExit = true;
				for (int id = 0; id < nodeNum; id++)
					if (csma_nodes[0][id].totalBuffer != 0)
					{
						shouldExit = false;
						break;
					}
				if (shouldExit)
					break;
			}

			for (int id = 0; id < nodeNum; id++)
			{
				switch (this->csma_nodes[0][id].state)
				{
				case STATE_IDLE:
					process_idle(id, time);
					break;
				case STATE_DIFS:
					process_difs(id, time);
					break;
				case STATE_CW  :
					process_cw(id, time);
					break;
				case STATE_DATA:
					process_data(id, time);
					break;
				case STATE_ACK :
					process_ack(id, time);
					break;
				case STATE_NAV :
					process_nav(id, time);
					break;
				}
			}
			copyState();							// copy the state of [1] to [0];
		}

		this->receiveCount = 0;
		this->sendCount = 0;
		this->collisionCount = 0;
		for (int i = 0; i < nodeNum; i++)
		{
			receiveCount += this->csma_nodes[0][i].totalReceive;
			sendCount += this->csma_nodes[0][i].totalSend;
			collisionCount += this->csma_nodes[0][i].totalCollision;
		}
		
		msgout << receiveCount << " " << sendCount << " " << collisionCount << endl;
		cout << receiveCount << " " << sendCount << " " << collisionCount << endl;

		msgout.close();
	}

	void copyNode(const CSMA_node& from, CSMA_node &to)
	{
		//Position pos;
		//int globalID;
		//int domainID;
	
		to.totalBuffer = from.totalBuffer;
		to.totalSend = from.totalSend;
		to.totalReceive = from.totalReceive;
		to.totalCollision = from.totalCollision;

		to.nodeTraffic.clear();
		for (deque<Traffic>::const_iterator it = from.nodeTraffic.begin(); it != from.nodeTraffic.end(); it++)
		{
			to.nodeTraffic.push_back(*it);
		}

		to.isSender = from.isSender;
		to.sendTo = from.sendTo;
		to.receiveFrom = from.receiveFrom;;

		to.state = from.state;
		to.countingDown = from.countingDown;
		to.prevCountingDown = from.prevCountingDown;

		to.conWin = from.conWin;
	}

	void copyState()
	{
		for (int i = 0; i < nodeNum; i++)		
		{
			copyNode(csma_nodes[1][i], csma_nodes[0][i]);
		}
	}

	void process_ack(int id, int time)
	{
		CSMA_node &node_old = csma_nodes[0][id];
		CSMA_node &node_new = csma_nodes[1][id];

		if (node_old.isSender)
		{
			// ack is collided, return DIFS
			if (checkCollision(node_new.globalID, node_new.sendTo) && node_new.countingDown > 0)
			{
				collisionMessage(time, node_new);
				updateStateMsg(time, node_new, "ACK-->DIFS");
				node_new.state = STATE_DIFS;
				if (node_new.conWin < CW_MAX)
					node_new.conWin *= 2;
				else 
					node_new.conWin = CW_MAX;
				node_new.countingDown = T_DIFS;
			}
			else
			{
				if (node_new.countingDown > 0)
					node_new.countingDown--;
				else
				{
					// finish this packet, return idle, reset contention window
					updateStateMsg(time, node_new, "ACK-->IDLE");
					node_new.isSender = false;
					node_new.state = STATE_IDLE;
					node_new.conWin = CW_MIN;
					node_new.countingDown = 0;
					node_new.totalSend++;		// finish sending one more packet
					node_new.nodeTraffic[0].pkt_number--;
					finishPktMsg(time, node_new.globalID, node_new.sendTo);
					if (node_new.nodeTraffic[0].pkt_number == 0)
					{
						node_new.nodeTraffic.pop_front();
					}
					node_new.sendTo = -1;
					node_new.receiveFrom = -1;
					node_new.totalBuffer--;
					return;
				}
			}
		}
		else
		{
			if (node_new.countingDown > 0)
				node_new.countingDown--;
			else
			{
				// done, back to IDLE
				node_new.isSender = false;
				updateStateMsg(time, node_new, "ACK-->IDLE");
				node_new.state = STATE_IDLE;
				node_new.sendTo = -1;
				node_new.receiveFrom = -1;
				node_new.countingDown = 0;
				return;	
			}
		}
	}

	void process_data(int id, int time)
	{
		CSMA_node &node_old = csma_nodes[0][id];
		CSMA_node &node_new = csma_nodes[1][id];
		if (node_new.isSender)
		{
			if (node_new.countingDown > 0)
				node_new.countingDown--;
			else
			{
				CSMA_node receiver = csma_nodes[0][node_new.sendTo];
				if (receiver.state == STATE_ACK && receiver.receiveFrom == node_new.globalID)
				{
					updateStateMsg(time, node_new, "DATA-->ACK");
					node_new.state = STATE_ACK;
					node_new.countingDown = receiver.countingDown - 1;		// they will go to IDLE together
					return;
				}
				else	// no ack, back to DIFS
				{
					node_new.totalCollision++;
					collisionMessage(time, node_new);
					updateStateMsg(time, node_new, "DATA-->DIFS");
					node_new.state = STATE_DIFS;
					if (node_new.conWin < CW_MAX)
						node_new.conWin *= 2;
					else 
						node_new.conWin = CW_MAX;
					node_new.countingDown = T_DIFS;
					return;					
				}
			}
		}
		else
		{
			// data is collided, return idle
			if (checkCollision(node_new.globalID, node_new.receiveFrom) && node_new.countingDown > T_SIFS)
			{
				updateStateMsg(time, node_new, "DATA-->IDLE");
				node_new.isSender = false;
				node_new.sendTo = -1;
				node_new.receiveFrom = -1;
				node_new.state = STATE_IDLE;
				node_new.countingDown = 0;			
			}
			else
			{
				if (node_new.countingDown > 0)
					node_new.countingDown--;
				else
				{
					// send ACK
					updateStateMsg(time, node_new, "DATA-->ACK");
					node_new.state = STATE_ACK;
					node_new.countingDown = T_ACK;
					node_new.totalReceive++;			// received one more packet
				}
			}
		}
	}

	void process_nav(int id, int time)
	{
		CSMA_node &node_old = csma_nodes[0][id];
		CSMA_node &node_new = csma_nodes[1][id];

		// check whether is a receiver
		int senderID = -1;
		bool isReceiver = checkReceiver(node_new.globalID, senderID);

		if (isReceiver)
		{
			// check whether the DATA is received successfully
			bool isCollision = checkCollision(node_new.globalID, senderID);

			if (isReceiver && !isCollision)
			{
				node_new.isSender = false;
				updateStateMsg(time, node_new, "NAV-->DATA");
				node_new.state = STATE_DATA;
				node_new.receiveFrom = senderID;
				node_new.countingDown = this->csma_nodes[0][senderID].countingDown - 2;
				return;
			}
		}

		if (node_new.countingDown > 0)
		{
			// wait for the channel to be idel for T_DIFS, check the channel is busy due to other reasons
			bool isBusy = checkCollision(node_new.globalID, node_new.globalID);

			if (isBusy)
				node_new.countingDown = T_DIFS + T_SLOT;
			else
				node_new.countingDown--;
		}
		else	// count down to 0, go to contenton window
		{
			updateStateMsg(time, node_new, "NAV-->CW");
			node_new.state = STATE_CW;
			node_new.countingDown = node_new.prevCountingDown;
			return;
		}
	}

	void process_cw(int id, int time)
	{
		CSMA_node &node_old = csma_nodes[0][id];
		CSMA_node &node_new = csma_nodes[1][id];

		// check whether is a receiver
		int senderID = -1;
		bool isReceiver = checkReceiver(node_new.globalID, senderID);

		if (isReceiver)
		{
			// check whether the DATA is received successfully
			bool isCollision = checkCollision(node_new.globalID, senderID);

			if (isReceiver && !isCollision)
			{
				node_new.isSender = false;
				updateStateMsg(time, node_new, "CW-->DATA");
				node_new.state = STATE_DATA;
				node_new.receiveFrom = senderID;
				node_new.countingDown = this->csma_nodes[0][senderID].countingDown - 2;
				return;
			}
		}

		if (node_new.countingDown > 0)
		{
			// only count down when the medium is free
			bool isBusy = checkCollision(node_new.globalID, node_new.globalID);
			if (!isBusy)
				node_new.countingDown--;
			else
			{	// decrease the counter only when the entire T_SLOT is free
				if (node_new.countingDown % T_SLOT != 0)
				{
					node_new.countingDown = (node_new.countingDown / T_SLOT + 1) * T_SLOT;
				}
				// wait for DIFS + T_SLOT before resume counting down
				updateStateMsg(time, node_new, "CW-->NAV");
				node_new.state = STATE_NAV;				
				node_new.prevCountingDown = node_new.countingDown;
				node_new.countingDown = T_DIFS + T_SLOT;
			}
		}
		else	// count down to 0, go to data
		{
			bool isBusy = checkCollision(node_new.globalID, node_new.globalID);
			if (!isBusy)
			{
				node_new.isSender = true;
				updateStateMsg(time, node_new, "CW-->DATA");
				node_new.state = STATE_DATA;
				node_new.sendTo = node_new.nodeTraffic[0].receiver_id;
				node_new.countingDown = this->t_data + T_SIFS;
			}
			else
			{
				updateStateMsg(time, node_new, "CW-->CW");
				node_new.state = STATE_CW;
				if (node_new.conWin < CW_MAX)
					node_new.conWin *= 2;
				else 
					node_new.conWin = CW_MAX;
				node_new.countingDown = (rand() % node_new.conWin + 1) * T_SLOT;			
			}
		}
	}

	void process_difs(int id, int time)
	{
		CSMA_node &node_old = csma_nodes[0][id];
		CSMA_node &node_new = csma_nodes[1][id];
		// check whether is a receiver
		int senderID = -1;
		bool isReceiver = checkReceiver(node_new.globalID, senderID);

		if (isReceiver)
		{
			// check whether the DATA is received successfully
			bool isCollision = checkCollision(node_new.globalID, senderID);

			if (isReceiver && !isCollision)
			{
				node_new.isSender = false;
				updateStateMsg(time, node_new, "DIFS-->DATA");
				node_new.state = STATE_DATA;
				node_new.receiveFrom = senderID;
				node_new.countingDown = this->csma_nodes[0][senderID].countingDown - 2;
				return;
			}
		}
		
		if (node_new.countingDown > 0)
		{
			// wait for the channel to be idel for T_DIFS, check the channel is busy due to other reasons
			bool isBusy = checkCollision(node_new.globalID, node_new.globalID);

			if (isBusy)
				node_new.countingDown = T_DIFS;
			else
				node_new.countingDown--;
		}
		else	// count down to 0, go to contenton window
		{
			updateStateMsg(time, node_new, "DIFS-->CW");
			node_new.state = STATE_CW;
			node_new.countingDown = (rand() % node_new.conWin + 1) * T_SLOT;
			return;
		}
	}

	void process_idle(int id, int time)
	{
		CSMA_node &node_old = csma_nodes[0][id];
		CSMA_node &node_new = csma_nodes[1][id];

		// nothing to send
		if (node_new.totalBuffer == 0)
		{
			node_new.isSender = false;

			// check whether to receiver
			int senderID = -1;
			bool isReceiver = checkReceiver(node_new.globalID, senderID);

			// not receiver, just remain idle
			if (!isReceiver)
				return;

			// check whether the DATA is received successfully
			bool isCollision = checkCollision(node_new.globalID, senderID);

			if (isReceiver && !isCollision)
			{
				updateStateMsg(time, node_new, "IDLE-->DATA");
				node_new.state = STATE_DATA;
				node_new.receiveFrom = senderID;
				node_new.countingDown = this->csma_nodes[0][senderID].countingDown - 2;
				return;
			}
		}
		else // has something to send
		{
			node_new.isSender = true;
			node_new.sendTo = node_new.nodeTraffic[0].receiver_id;
			updateStateMsg(time, node_new, "IDLE-->DIFS");
			node_new.state = STATE_DIFS;
			node_new.countingDown = T_DIFS;
			return;
		}
	}

	// whether some neighbor send data to node
	bool checkReceiver(int receiverID, int& senderID)
	{
		CSMA_node receiver = this->csma_nodes[0][receiverID];
		for (vector<int>::iterator it = this->csma_neighborList[receiverID].begin(); it != this->csma_neighborList[receiverID].end(); it++)
		{
			CSMA_node neighbor = csma_nodes[0][*it];

			// some neighbor has sent DATA to node
			if (neighbor.isSender && neighbor.state == STATE_DATA 
				&& neighbor.countingDown == this->t_data + T_SIFS && neighbor.sendTo == receiver.globalID)
			{
				senderID = neighbor.globalID;
				return true;
			}
		}
		return false;
	}

	// whether the link sender-->receiver is failed due to collision
	bool checkCollision(int receiverID, int senderID)
	{
		CSMA_node receiver = this->csma_nodes[0][receiverID];
		CSMA_node sender = this->csma_nodes[0][senderID];
		for (vector<int>::iterator it = this->csma_neighborList[receiverID].begin(); it != this->csma_neighborList[receiverID].end(); it++)
		{
			CSMA_node neighbor = csma_nodes[0][*it];
			if (neighbor.globalID == sender.globalID)
				continue;

			if ((neighbor.isSender && neighbor.state == STATE_DATA && neighbor.countingDown > T_SIFS) 
				|| (!neighbor.isSender && neighbor.state == STATE_ACK && neighbor.countingDown > 0)
				)
			{
				return true;
			}
		}
		return false;
	}

	void collisionMessage(int time, const CSMA_node& node)
	{
		//cout << time << " COLLISION: " << node.globalID << "-->" << node.sendTo << endl;
		//msgout << time << " COLLISION: " << node.globalID << "-->" << node.sendTo << endl;
	}

	void updateStateMsg(int time, const CSMA_node& node, string msg)
	{
		// Use this to print something
		//string not = "";
		//if (!node.isSender)
		//	not += " NOT";
		//cout << time << " node " << node.globalID << "(" << node.domainID << ") is" << not << " sender: " << msg << endl;
		//msgout << time << " node " << node.globalID << "(" << node.domainID << ") is" << not << " sender: " << msg << endl;
	}

	void finishPktMsg(int time, int from, int to)
	{
		cout << time << " FINISH: " << from << "-->" << to << endl;
		msgout << time << " FINISH: " << from << "-->" << to << endl;
	}
};